﻿using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using JasLib.Extensions;
using System.Threading;

namespace JasLib.Wpf
{
    #region MarkupExtension converters

    /*****************************************************************************/
    /// <summary>
    /// http://robertmclaws.com/simplify-your-wpf-converters-with-the-convertiblemarkupextension
    /// </summary>
    [MarkupExtensionReturnType(typeof(IValueConverter))]
    public abstract class SimpleSingleValueConverterExtensionBase : ThisExtension, IValueConverter
    {
        protected bool _bFlip = false;
        /// <summary>
        /// Whether or not to swap the simplified Convert and ConvertBack override calls
        /// to effectively reverse the conversion.
        /// </summary>
        public bool Flip
        {
            get { return _bFlip; }
            set { _bFlip = value; }
        }

        public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (_bFlip)
                return ConvertBack(value);
            else
                return Convert(value);
        }

        public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (_bFlip)
                return Convert(value);
            else
                return ConvertBack(value);
        }

        public virtual object Convert(object value)
        {
            throw new NotImplementedException();
        }

        public virtual object ConvertBack(object value)
        {
            throw new NotImplementedException();
        }
    }

    /*****************************************************************************/
    /// <summary>
    /// http://robertmclaws.com/simplify-your-wpf-converters-with-the-convertiblemarkupextension
    /// </summary>
    [MarkupExtensionReturnType(typeof(IMultiValueConverter))]
    public abstract class SimpleMultiValueConverterExtensionBase : ThisExtension, IMultiValueConverter
    {
        public virtual object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            return Convert(values);
        }

        public virtual object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            return ConvertBack(value);
        }

        public virtual object Convert(object[] values)
        {
            throw new NotImplementedException();
        }

        public virtual object[] ConvertBack(object value)
        {
            throw new NotImplementedException();
        }
    }

    /*****************************************************************************/
    [MarkupExtensionReturnType(typeof(IMultiValueConverter))]
    public class SelectFirstBindingMultiValueConverterExtension : SimpleMultiValueConverterExtensionBase
    {
        public override object Convert(object[] values)
        {
            if (values.Length == 0)
                return null;
            return values[0];
        }
    }

    #endregion

    /*****************************************************************************/
    /// <summary>
    /// A convenient base implementation of IValueConverter that stubs out unimplemented methods
    /// and hides unnecessary parameters.
    /// </summary>
    public abstract class SimpleSingleValueConverterViewModelBase : ViewModelBase<SimpleSingleValueConverterViewModelBase>, IValueConverter
    {
        private bool _bFlip = false;
        /// <summary>
        /// Whether or not to swap the simplified Convert and ConvertBack override calls
        /// to effectively reverse the conversion.
        /// </summary>
        public bool Flip
        {
            get { return _bFlip; }
            set { SetProperty(ref _bFlip, value, () => Flip); }
        }

        #region IValueConverter Members

        public virtual object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (_bFlip)
                return ConvertBack(value);
            else
                return Convert(value);
        }

        public virtual object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (_bFlip)
                return Convert(value);
            else
                return ConvertBack(value);
        }

        #endregion

        public virtual object Convert(object value)
        {
            throw new NotImplementedException();
        }

        public virtual object ConvertBack(object value)
        {
            throw new NotImplementedException();
        }
    }

    /*****************************************************************************/
    /// <summary>
    /// A convenient base implementation of <see cref="IMultiValueConverter"/> that stubs out unimplemented methods
    /// and hides unnecessary parameters.
    /// </summary>
    public abstract class SimpleMultiValueConverterViewModelBase : ViewModelBase<SimpleSingleValueConverterViewModelBase>, IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public virtual object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            VerifyAccess();
            return Convert(values);
        }

        public virtual object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            VerifyAccess();
            return ConvertBack(value);
        }

        #endregion

        public virtual object Convert(object[] values)
        {
            throw new NotImplementedException();
        }

        public virtual object[] ConvertBack(object value)
        {
            throw new NotImplementedException();
        }
    }

    /*****************************************************************************/
    /// <summary>
    /// When used as a multi-value converter, only the first item can be an IMultiValueConverter.
    /// http://www.codeproject.com/KB/WPF/PipingValueConverters_WPF.aspx
    /// NOTE: This class is not finished yet.
    /// </summary>
    [ContentProperty("Converters")]
    public class ValueConverterPipeline : ViewModelBase<SimpleSingleValueConverterViewModelBase>, IValueConverter, IMultiValueConverter
    {
        private readonly ObservableCollection<object> _converters = new ObservableCollection<object>();
        public IList Converters
        {
            get { return _converters; }
        }

        #region IValueConverter implementation

        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            object objOutput = value;

            for (int iIndex = 0; iIndex < _converters.Count; iIndex++)
            {
                IValueConverter valueConverter = (_converters[iIndex] as IValueConverter);
                objOutput = valueConverter.Convert(objOutput, targetType, null, culture);
            }

            return objOutput;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IMultiValueConverter implementation

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (_converters.Count == 0)
                throw new InvalidOperationException();

            IMultiValueConverter multiValueConverter = (_converters[0] as IMultiValueConverter);
            if (multiValueConverter == null)
                throw new InvalidOperationException();

            /// TODO: MultiValueConverters need an attribute describing their output type.
            object objOutput = multiValueConverter.Convert(values, targetType, null, culture);

            for (int iIndex = 1; iIndex < _converters.Count; iIndex++)
            {
                IValueConverter valueConverter = (_converters[iIndex] as IValueConverter);
                objOutput = valueConverter.Convert(objOutput, targetType, null, culture);
            }

            return objOutput;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

    /*****************************************************************************/
    /// <summary>
    /// http://www.codeproject.com/Articles/92944/A-Universal-Value-Converter-for-WPF.aspx
    /// </summary>
    [ValueConversion(typeof(object), typeof(object))]
    public class UniversalValueConverter : SimpleSingleValueConverterExtensionBase
    {
        public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            try
            {
                // obtain the converter for the target type
                TypeConverter converter = TypeDescriptor.GetConverter(targetType);

                // determine if the supplied value is of a suitable type
                if (converter.CanConvertFrom(value.GetType()))
                {
                    // return the converted value
                    return converter.ConvertFrom(value);
                }
                else
                {
                    // try to convert from the string representation
                    return converter.ConvertFrom(value.ToString());
                }
            }
            catch (Exception)
            {
                return value;
            }
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(object), typeof(bool))]
    public class NullableInRangeToNullableBooleanConverter<T> : SimpleSingleValueConverterExtensionBase
        where T : struct, IComparable<T>
    {
        public T? Lower { get; set; }
        public T? Upper { get; set; }

        public override object Convert(object value)
        {
            T? valueT = (T?)value;

            if (valueT == null)
            {
                return false;
            }

            if (Lower != null)
            {
                if (valueT.Value.CompareTo(this.Lower.Value) < 0)
                    return false;
            }

            if (Upper != null)
            {
                if (valueT.Value.CompareTo(this.Upper.Value) > 0)
                    return false;
            }

            return true;
        }
    }

    [ValueConversion(typeof(int?), typeof(bool))]
    public class NullableInt32InRangeToNullableBooleanConverter : NullableInRangeToNullableBooleanConverter<int>
    {
    }

    [ValueConversion(typeof(long?), typeof(bool))]
    public class NullableInt64InRangeToNullableBooleanConverter : NullableInRangeToNullableBooleanConverter<long>
    {
    }

    /*****************************************************************************/
    [ValueConversion(typeof(object), typeof(bool))]
    public class InRangeToNullableBooleanConverter<T> : SimpleSingleValueConverterExtensionBase
        where T : IComparable<T>
    {
        public T Lower { get; set; }
        public T Upper { get; set; }

        public override object Convert(object value)
        {
            T valueT = (T)value;
            return valueT.InRange(this.Lower, this.Upper);
        }
    }

    /*****************************************************************************/
    /// <summary>
    /// http://blogs.magnatis.com/tag/value-converter
    /// </summary>
    [ValueConversion(typeof(object), typeof(object))]
    public class NullToUnsetConverter : SimpleSingleValueConverterExtensionBase
    {
        public override object Convert(object value)
        {
            return value ?? DependencyProperty.UnsetValue;
        }
    }

    /*****************************************************************************/
    public abstract class EmptyOrNullToValueConverterBase : SimpleSingleValueConverterExtensionBase
    {
        protected static ConcurrentDictionary<Type, PropertyInfo> s_countCollectionPropertyInfos = new ConcurrentDictionary<Type, PropertyInfo>();
    }

    /*****************************************************************************/
    public class EmptyOrNullToValueConverter<T> : EmptyOrNullToValueConverterBase
    {
        private T _nullValue = default(T);
        public T NullValue
        {
            get { return _nullValue; }
            set { _nullValue = value; }
        }

        private T _emptyValue = default(T);
        public T EmptyValue
        {
            get { return _emptyValue; }
            set { _emptyValue = value; }
        }

        private T _nonEmptyValue = default(T);
        public T NonEmptyValue
        {
            get { return _nonEmptyValue; }
            set { _nonEmptyValue = value; }
        }

        public override object Convert(object value)
        {
            if (value == null)
                return _nullValue;

            try
            {
                /// Different types have special meanings for "empty".
                if (value is ICollection)
                {
                    /// NOTE: not all generic collections derive from ICollection.
                    if ((value as ICollection).Count == 0)
                        return _emptyValue;
                }
                else if (value is string)
                {
                    if ((value as string).Length == 0)
                        return _emptyValue;
                }
                else if (value.IsType(typeof(ICollection<>)))
                {
                    /// Gonna have to reflect for the Count property. Sloppy for sure, slow in volume.
                    Type valueType = value.GetType();
                    var countPropertyInfo = s_countCollectionPropertyInfos.GetOrAdd(
                        valueType,
                        t => t.GetProperty("Count", typeof(int)));
                        
                    int iCount = (int)countPropertyInfo.GetValue(value, null);
                    if (iCount == 0)
                        return _emptyValue;
                }
                else if (value is IConvertible)
                {
                    long lFinalValue = (long)System.Convert.ChangeType(value, typeof(long));
                    if (lFinalValue == 0)
                        return _emptyValue;
                }
                else
                {
                }
            }
            catch
            {
                throw;
            }

            /// It's not null, and it's not an empty number.
            return _nonEmptyValue;
        }

        public EmptyOrNullToValueConverter(T nullValue, T emptyValue, T nonEmptyValue)
        {
            _nullValue = nullValue;
            _emptyValue = emptyValue;
            _nonEmptyValue = nonEmptyValue;
            return;
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(object), typeof(object))]
    public class EmptyOrNullToObjectConverter : EmptyOrNullToValueConverter<object>
    {
        public EmptyOrNullToObjectConverter()
            : base(null, null, null)
        {
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(object), typeof(bool?))]
    public class EmptyOrNullToNullableBooleanConverter : EmptyOrNullToValueConverter<bool?>
    {
        public EmptyOrNullToNullableBooleanConverter()
            : base(null, false, true)
        {
        }

        public EmptyOrNullToNullableBooleanConverter(bool? nullValue, bool? emptyValue, bool? nonEmptyValue)
            : base(nullValue, emptyValue, nonEmptyValue)
        {
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(object), typeof(System.Windows.Visibility))]
    public class EmptyOrNullToVisibilityConverter : EmptyOrNullToValueConverter<System.Windows.Visibility>
    {
        public EmptyOrNullToVisibilityConverter()
            : base(Visibility.Hidden, Visibility.Collapsed, Visibility.Visible)
        {
        }

        public EmptyOrNullToVisibilityConverter(Visibility nullValue, Visibility emptyValue, Visibility nonEmptyValue)
            : base(nullValue, emptyValue, nonEmptyValue)
        {
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(sbyte), typeof(string))]
    [ValueConversion(typeof(byte), typeof(string))]
    [ValueConversion(typeof(short), typeof(string))]
    [ValueConversion(typeof(ushort), typeof(string))]
    [ValueConversion(typeof(int), typeof(string))]
    [ValueConversion(typeof(uint), typeof(string))]
    [ValueConversion(typeof(long), typeof(string))]
    [ValueConversion(typeof(ulong), typeof(string))]
    public class IntegralDataSizeToShortDisplayStringConverter : SimpleSingleValueConverterExtensionBase
    {
        public override object Convert(object value)
        {
            if (value == null)
                return DependencyProperty.UnsetValue;

            ulong ulFinalValue = (value is ulong) ? (ulong)value : (ulong)Math.Abs((long)System.Convert.ChangeType(value, typeof(long)));

            decimal units;
            string strUnitName;
            GeneralUtilities.FormatByteSize(ulFinalValue, out units, out strUnitName);
            return string.Format("{0:0.#} {1}", units, strUnitName);
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(sbyte), typeof(string))]
    [ValueConversion(typeof(byte), typeof(string))]
    [ValueConversion(typeof(short), typeof(string))]
    [ValueConversion(typeof(ushort), typeof(string))]
    [ValueConversion(typeof(int), typeof(string))]
    [ValueConversion(typeof(uint), typeof(string))]
    [ValueConversion(typeof(long), typeof(string))]
    [ValueConversion(typeof(ulong), typeof(string))]
    public class IntegralDataSizeToLongDisplayStringConverter : SimpleSingleValueConverterExtensionBase
    {
        public override object Convert(object value)
        {
            ulong ulFinalValue = (value is ulong) ? (ulong)value : (ulong)Math.Abs((long)System.Convert.ChangeType(value, typeof(long)));
            return string.Format("{0:N0} bytes", ulFinalValue);
        }
    }

    /*****************************************************************************/
    public abstract class NullableBooleanBinaryExpressionConverter : SimpleMultiValueConverterExtensionBase
    {
        private bool _bIgnoreNullOrUnset;
        public bool IgnoreNullOrUnset
        {
            get { return _bIgnoreNullOrUnset; }
            set { _bIgnoreNullOrUnset = value; }
        }

        private bool? _bNullPromotionValue;
        public bool? NullPromotionValue
        {
            get { return _bNullPromotionValue; }
            set { _bNullPromotionValue = value; }
        }

        private bool? _bUnsetPromotionValue;
        public bool? UnsetPromotionValue
        {
            get { return _bUnsetPromotionValue; }
            set { _bUnsetPromotionValue = value; }
        }

        public NullableBooleanBinaryExpressionConverter(
            bool bIgnoreNullOrUnset,
            bool? bNullPromotionValue,
            bool? bUnsetPromotionValue)
        {
            _bIgnoreNullOrUnset = bIgnoreNullOrUnset;
            _bNullPromotionValue = bNullPromotionValue;
            _bUnsetPromotionValue = bUnsetPromotionValue;
            return;
        }

        protected abstract bool? OnApplyExpression(bool? bOperand1, bool? bOperand2);

        public override object Convert(object[] values)
        {
            bool? bFinalResult = null;
            for (int iIndex = 0; iIndex < values.Length; iIndex++)
            {
                object objThis = values[iIndex];
                bool? bThisResult;

                if (objThis == DependencyProperty.UnsetValue)
                {
                    if (_bIgnoreNullOrUnset)
                        continue;
                    bThisResult = _bUnsetPromotionValue;
                }
                else if (objThis == null)
                {
                    if (_bIgnoreNullOrUnset)
                        continue;
                    bThisResult = _bNullPromotionValue;
                }
                else
                {
                    bThisResult = (bool?)objThis;
                }

                if (iIndex == 0)
                    bFinalResult = bThisResult;
                else
                    bFinalResult = OnApplyExpression(bFinalResult, bThisResult);
            }
            return bFinalResult;
        }

    }

    /*****************************************************************************/
    public class NullableBooleanAndConverter : NullableBooleanBinaryExpressionConverter
    {
        public NullableBooleanAndConverter()
            : base(false, false, false)
        {
        }

        public NullableBooleanAndConverter(
            bool bIgnoreNullOrUnset,
            bool? bNullPromotionValue,
            bool? bUnsetPromotionValue)
            : base(bIgnoreNullOrUnset, bNullPromotionValue, bUnsetPromotionValue)
        {
        }

        protected override bool? OnApplyExpression(bool? bOperand1, bool? bOperand2)
        {
            return (bOperand1 & bOperand2);
        }
    }

    /*****************************************************************************/
    public class NullableBooleanOrConverter : NullableBooleanBinaryExpressionConverter
    {
        public NullableBooleanOrConverter()
            : base(false, false, false)
        {
        }

        protected override bool? OnApplyExpression(bool? bOperand1, bool? bOperand2)
        {
            return (bOperand1 | bOperand2);
        }
    }

    /*****************************************************************************/
    public class NullableBooleanXorConverter : NullableBooleanBinaryExpressionConverter
    {
        public NullableBooleanXorConverter()
            : base(false, false, false)
        {
        }

        protected override bool? OnApplyExpression(bool? bOperand1, bool? bOperand2)
        {
            return (bOperand1 ^ bOperand2);
        }
    }

    /*****************************************************************************/
    public class NullableBooleanNotConverter : SimpleSingleValueConverterExtensionBase
    {
        public override object Convert(object value)
        {
            if (value == null || !(value is bool))
                return null;
            else
                return !((bool)value);
        }

        public override object ConvertBack(object value)
        {
            /// No need to duplicate.
            return Convert(value);
        }
    }

    /*****************************************************************************/
    public class NullableBooleanConverter<T> : SimpleSingleValueConverterExtensionBase
    {
        private static Func<T, T, bool> _equalityFunc = EqualityComparer<T>.Default.Equals;

        private bool _bTrueValueSet = false;
        private T _trueValue = default(T);
        public T TrueValue
        {
            get { return _trueValue; }
            set
            {
                _trueValue = value;
                _bTrueValueSet = true;
            }
        }

        private bool _bFalseValueSet = false;
        private T _falseValue = default(T);
        public T FalseValue
        {
            get { return _falseValue; }
            set
            {
                _falseValue = value;
                _bFalseValueSet = true;
            }
        }

        private bool _bNullValueSet = false;
        private T _nullValue = default(T);
        public T NullValue
        {
            get { return _nullValue; }
            set
            {
                _nullValue = value;
                _bNullValueSet = true;
            }
        }

        public NullableBooleanConverter()
        {
        }

        public NullableBooleanConverter(T trueValue, T falseValue, T nullValue)
        {
            TrueValue = trueValue;
            FalseValue = falseValue;
            NullValue = nullValue;
            return;
        }

        public override object Convert(object value)
        {
            if (value == null)
                return (_bNullValueSet ? _nullValue : DependencyProperty.UnsetValue);
            else if (value is bool)
            {
                bool bValue = (bool)value;
                if (bValue)
                    return (_bTrueValueSet ? _trueValue : DependencyProperty.UnsetValue);
                else
                    return (_bFalseValueSet ? _falseValue : DependencyProperty.UnsetValue);
            }
            else
                return DependencyProperty.UnsetValue;
        }

        public override object ConvertBack(object value)
        {
            T tvalue = (T)value;

            if (_equalityFunc(tvalue, _trueValue))
                return true;
            else if (_equalityFunc(tvalue, _falseValue))
                return false;
            else
                return null;
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(bool), typeof(object))]
    public class NullableBooleanToObjectConverter : NullableBooleanConverter<object>
    {
    }

    /*****************************************************************************/
    [ValueConversion(typeof(bool), typeof(double))]
    public class NullableBooleanToDoubleConverter : NullableBooleanConverter<double>
    {
    }

    /*****************************************************************************/
    [ValueConversion(typeof(bool), typeof(string))]
    public class NullableBooleanToStringConverter : NullableBooleanConverter<string>
    {
    }

    /*****************************************************************************/
    [ValueConversion(typeof(bool), typeof(System.Windows.Visibility))]
    public class NullableBooleanToVisibilityConverter : NullableBooleanConverter<System.Windows.Visibility>
    {
        public NullableBooleanToVisibilityConverter()
            : base(Visibility.Visible, Visibility.Hidden, Visibility.Collapsed)
        {
        }

        public NullableBooleanToVisibilityConverter(
            System.Windows.Visibility nullValue,
            System.Windows.Visibility emptyValue,
            System.Windows.Visibility nonEmptyValue)
            : base(nullValue, emptyValue, nonEmptyValue)
        {
        }
    }

    /*****************************************************************************************************/
    [MarkupExtensionReturnType(typeof(string))]
    public class JoinStringsMultiValueConverterExtension : SimpleMultiValueConverterExtensionBase
    {
        private string _strConnector = null;
        private bool _bForceSourceConversionToString = false;

        public JoinStringsMultiValueConverterExtension()
        {
            return;
        }

        public JoinStringsMultiValueConverterExtension(
            string strConnector,
            bool bForceSourceConversionToString = false)
        {
            _strConnector = strConnector;
            _bForceSourceConversionToString = bForceSourceConversionToString;
            return;
        }

        public override object Convert(object[] values)
        {
            IEnumerable<string> strings = _bForceSourceConversionToString ? values.ToStrings() : values.OfType<string>();
            return strings.Join(_strConnector);
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(object), typeof(string))]
    public class ObjectToStringConverter : SimpleSingleValueConverterExtensionBase
    {
        public string Format { get; set; }

        public override object Convert(object value)
        {
            if (value == null)
                return null;

            if (!this.Format.IsNullOrEmpty())
                return string.Format(this.Format, value);
            else
                return value.ToString();
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(TimeSpan), typeof(string))]
    public class TimeSpanToLongStringConverter : SimpleSingleValueConverterExtensionBase
    {
        public override object Convert(object value)
        {
            if (value != null)
                return ((TimeSpan)value).ToLongString();
            else
                return null;
        }

        public override object ConvertBack(object value)
        {
            return null;
        }
    }

    /*****************************************************************************/
    [ValueConversion(typeof(DateTime), typeof(DateTime))]
    public class UniversalDateTimeToLocalDateTimeConverter : SimpleSingleValueConverterExtensionBase
    {
        public override object Convert(object value)
        {
            if (value == null)
                return DateTime.Now;

            DateTime utctime = (DateTime)value;
            return utctime.ToLocalTime();
        }

        public override object ConvertBack(object value)
        {
            if (value == null)
                return DateTime.UtcNow;

            DateTime localTime = (DateTime)value;
            return localTime.ToUniversalTime();
        }
    }
}
